//"The contents of this file are subject to the Mozilla Public License
//Version 1.1 (the "License"); you may not use this file except in
//compliance with the License. You may obtain a copy of the License at
//http://www.mozilla.org/MPL/

//Software distributed under the License is distributed on an "AS IS"
//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//License for the specific language governing rights and limitations
//under the License.

//The Original Code is TSharp.

//The Initial Developer of the Original Code is Mar3ek (http://mar3ek.wordpress.com).
//Portions created by Mar3ek are Copyright (C) 2010
//Mar3ek (http://mar3ek.wordpress.com). All Rights Reserved.

//Contributor(s): ______________________________________.

//Alternatively, the contents of this file may be used under the terms
//of the _____ license (the  "[___] License"), in which case the
//provisions of [______] License are applicable instead of those
//above.  If you wish to allow use of your version of this file only
//under the terms of the [____] License and not to allow others to use
//your version of this file under the MPL, indicate your decision by
//deleting  the provisions above and replace  them with the notice and
//other provisions required by the [___] License.  If you do not delete
//the provisions above, a recipient may use your version of this file
//under either the MPL or the [___] License."

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;

namespace TSharp
{
    public partial class Twitter
    {
        #region Tweets

        /// <summary>
        /// Updates the authenticating user's status.
        /// </summary>
        /// <param name="status">Status to post</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>New status</returns>
        /// <remarks>A status update with text identical to the authenticating user's current status will be ignored to prevent duplicates. Status length must be 140 characters or less.</remarks>
        /// <exception cref="ArgumentOutOfRangeException"><c>Status text is longer than 140 characters</c>.</exception>
        public Tweet UpdateStatus(string status)
        {
            if (string.IsNullOrEmpty(status)) throw new ArgumentNullException(status);
            status = status.Trim();
            if (string.IsNullOrEmpty(status)) throw new ArgumentNullException(status);

            status = status.Normalize(NormalizationForm.FormC);
            if (status.Length > 140)
                throw new ArgumentOutOfRangeException(status, "Status text is longer than 140 characters");

            status = Globals.ProperUrlEscape(status);

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/statuses/update.xml",
                                        false,
                                        new KeyValuePair<string, string>("status", status));

            var doc = XDocument.Parse(result);
            return ParseStatus(doc.Element("status"));
        }

        /// <summary>
        /// Retweets a tweet.
        /// </summary>
        /// <param name="id">The numerical ID of the desired status</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Returns the original tweet</returns>
        public Tweet RetweetStatus(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");
            var result = SendRequest(RequestMethod.Post,
                                        string.Format("https://api.twitter.com/1/statuses/retweet/{0}.xml", id),
                                        false);

            return ParseStatus(XDocument.Parse(result).Element("status"));
        }

        /// <summary>
        /// Returns a single status, specified by the <i>id</i> parameter.
        /// </summary>
        /// <param name="id">ID of the status you wish to retweet</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Returns the original tweet with retweet details embedded.</returns>
        public Tweet ShowStatus(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");

            var result = SendRequest(RequestMethod.Get,
                                        string.Format("https://api.twitter.com/1/statuses/show/{0}.xml", id),
                                        true);

            return ParseStatus(XDocument.Parse(result).Element("status"));
        }

        /// <summary>
        /// Destroys the status specified by the <i>id</i> parameter.
        /// </summary>
        /// <param name="id">The numerical ID of the desired status</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <remarks>The authenticating user must be the author of the specified status.</remarks>
        public void DestroyStatus(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");
            SendRequest(RequestMethod.Post,
                        string.Format("https://api.twitter.com/1/statuses/destroy/{0}.xml", id),
                        false);
        }

        /// <summary>
        /// Returns up to 100 of the first retweets of a given tweet.
        /// </summary>
        /// <param name="id">The numerical ID of the desired status.</param>
        /// <param name="count">Number of tweets to retrieve (must be between 1 and 100)</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of retweets</returns>
        public Collection<Tweet> StatusRetweets(ulong id, uint count)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");
            if (count < 1 || count > 100) throw new ArgumentOutOfRangeException("count");
            var result = SendRequest(RequestMethod.Get,
                                        string.Format("https://api.twitter.com/1/statuses/retweets/{0}.xml", id),
                                        true,
                                        new KeyValuePair<string, string>("count", count.ToString()));
            return ParseTimeline(result);
        }

        ///<summary>
        /// Returns up to 100 of the first retweets of a given tweet.
        ///</summary>
        ///<param name="id">The numerical ID of the desired status</param>
        ///<exception cref="ArgumentOutOfRangeException"></exception>
        ///<returns>List of retweets</returns>
        public Collection<Tweet> StatusRetweets(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");
            var result = SendRequest(RequestMethod.Get,
                                        string.Format("https://api.twitter.com/1/statuses/retweets/{0}.xml", id),
                                        true);
            return ParseTimeline(result);
        }

        /// <summary>
        /// Shows user objects of up to 100 members who retweeted the status.
        /// </summary>
        /// <param name="id">The numerical ID of the desired status</param>
        /// <param name="count">Number of users to retrieve (must be between 1 and 100)</param>
        /// <returns>List of users who retweeted the specified status</returns>
        /// <exception cref="ArgumentOutOfRangeException"><c>id</c> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><c>count</c> is out of range.</exception>
        public Collection<TwitterUser> StatusRetweetedBy(ulong id, uint count)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");
            if (count > 100 || count < 1) throw new ArgumentOutOfRangeException("count");

            var responseString = SendRequest(RequestMethod.Get,
                                                string.Format(
                                                    "https://api.twitter.com/1/statuses/{0}/retweeted_by.xml", id),
                                                true,
                                                new KeyValuePair<string, string>("count", count.ToString()));
            var doc = XDocument.Parse(responseString);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        ///<summary>
        /// Shows user objects of up to 100 members who retweeted the status.
        ///</summary>
        ///<param name="id">The numerical ID of the desired status</param>
        ///<returns>List of users who retweeted the specified status</returns>
        ///<exception cref="ArgumentOutOfRangeException"></exception>
        public Collection<TwitterUser> StatusRetweetedBy(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");

            var responseString = SendRequest(RequestMethod.Get,
                                                string.Format(
                                                    "https://api.twitter.com/1/statuses/{0}/retweeted_by.xml", id),
                                                true);
            var doc = XDocument.Parse(responseString);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Show user ids of up to 100 users who retweeted the status.
        /// </summary>
        /// <param name="id">The numerical ID of the desired status</param>
        /// <param name="count">Number of IDs to retrieve (must be between 1 to 100)</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>IDs of users who retweeted the specified status</returns>
        public Collection<ulong> StatusRetweetedByIds(ulong id, uint count)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");
            if (count > 100 || count < 1) throw new ArgumentOutOfRangeException("count");

            var responseString = SendRequest(RequestMethod.Get,
                                                string.Format(
                                                    "https://api.twitter.com/1/statuses/{0}/retweeted_by/ids.xml", id),
                                                true,
                                                new KeyValuePair<string, string>("count", count.ToString()));

            var doc = XDocument.Parse(responseString);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<ulong>(doc.Element("ids").Elements("id").Select(idElement => ulong.Parse(idElement.Value)).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        ///<summary>
        /// Show user ids of up to 100 users who retweeted the status.
        ///</summary>
        ///<param name="id">The numerical ID of the desired status</param>
        ///<returns>IDs of users who retweeted the specified status</returns>
        ///<exception cref="ArgumentOutOfRangeException"></exception>
        public Collection<ulong> StatusRetweetedByIds(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");

            var responseString = SendRequest(RequestMethod.Get,
                                                string.Format(
                                                    "https://api.twitter.com/1/statuses/{0}/retweeted_by/ids.xml", id),
                                                true);

            var doc = XDocument.Parse(responseString);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<ulong>(doc.Element("ids").Elements("id").Select(idElement => ulong.Parse(idElement.Value)).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns the 20 most recent retweets posted by users the specified user follows.
        /// </summary>
        /// <param name="screenName">Screen name of the target user</param>
        /// <returns>Collection of retweets</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        public Collection<Tweet> GetRetweetedToUser(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/retweeted_to_user.xml",
                                        true, new KeyValuePair<string, string>("screen_name", screenName));
            return ParseTimeline(result);
        }

        /// <summary>
        /// Returns the 20 most recent retweets posted by users the specified user follows.
        /// </summary>
        /// <param name="userId">ID of the target user</param>
        /// <returns>Collection of retweets</returns>
        /// <exception cref="ArgumentOutOfRangeException"><c>userId</c> is out of range.</exception>
        public Collection<Tweet> GetRetweetedToUser(ulong userId)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/retweeted_to_user.xml",
                                        true, new KeyValuePair<string, string>("user_id", userId.ToString()));
            return ParseTimeline(result);
        }

        /// <summary>
        /// Returns the 20 most recent retweets posted by the specified user.
        /// </summary>
        /// <param name="screenName">Screen name of the target user</param>
        /// <returns>Collection of retweets</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        public Collection<Tweet> GetRetweetedByUser(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/retweeted_by_user.xml",
                                        true, new KeyValuePair<string, string>("screen_name", screenName));
            return ParseTimeline(result);
        }

        /// <summary>
        /// Returns the 20 most recent retweets posted by the specified user.
        /// </summary>
        /// <param name="userId">ID if the target user</param>
        /// <returns>Collection of retweets</returns>
        /// <exception cref="ArgumentOutOfRangeException"><c>userId</c> is out of range.</exception>
        public Collection<Tweet> GetRetweetedByUser(ulong userId)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/retweeted_by_user.xml",
                                        true, new KeyValuePair<string, string>("user_id", userId.ToString()));
            return ParseTimeline(result);
        }

        /// <summary>
        /// Returns a collection of replies and mentions related to the specified tweet.
        /// </summary>
        /// <returns>Collection of related tweets</returns>
        /// <exception cref="ArgumentOutOfRangeException"><c>tweetId</c> is out of range.</exception>
        /// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
        public Collection<Tweet> GetRelatedTweets(ulong tweetId)
        {
            // TODO: Implement after adding support for JSON reponses
            // endpoint /1/related_results/show/:id.{format} does not supprot XML responses

            throw new NotImplementedException();

            //if (tweetId < 1) throw new ArgumentOutOfRangeException("tweetId");

            //var result = SendRequest(RequestMethod.Get,
            //                            string.Format("https://api.twitter.com/1/related_results/show/{0}.json", tweetId),
            //                            true);
            //return ParseTimeline(result);
        }

        #endregion 

        /// <summary>
        /// Parses status from XML string
        /// </summary>
        /// <param name="statusElement">XML node defining Twitter status</param>
        /// <returns>Twitter status</returns>
        private static Tweet ParseStatus(XContainer statusElement)
        {
            Tweet result = null;
            if (statusElement != null)
            {
                // ReSharper disable PossibleNullReferenceException
                var text = HttpUtility.HtmlDecode(statusElement.Element("text").Value);
                var id = ulong.Parse(statusElement.Element("id").Value);

                var user = statusElement.Element("user");

                var time = statusElement.Element("created_at").Value;
                // ReSharper restore PossibleNullReferenceException
                var final = ParseTiwtterTime(time);

                ulong retweetCount = 0;
                var retweetCountElement = statusElement.Element("retweet_count");
                if (retweetCountElement != null)
                {
                    ulong.TryParse(retweetCountElement.Value, out retweetCount);
                }

                var retweeted = false;
                var retweetedElement = statusElement.Element("retweeted");
                if (retweetedElement != null)
                {
                    bool.TryParse(retweetedElement.Value, out retweeted);
                }
                result = new Tweet(id, text, ParseAuthor(user), final, retweetCount, retweeted);
                return result;
            }
            return result;
        }
    }
}